package cn.schoolwow.quickserver.handler;

import cn.schoolwow.quickserver.domain.Client;
import cn.schoolwow.quickserver.response.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.zip.GZIPOutputStream;

/**
 * http响应处理
 */
public class HttpResponseHandler implements Handler {
    private static Logger logger = LoggerFactory.getLogger(HttpResponseHandler.class);

    @Override
    public Handler handle(Client client) throws IOException {
        if(client.socket.isClosed()){
            logger.debug("[链接已关闭]套接字:{}",client.socket);
            return new RequestResponseLogHandler();
        }
        if (null != client.httpResponseMeta.headerString) {
            logger.trace("[请求头部报文已发送]结束");
            client.httpResponseMeta.outputStream.flush();
            return new RequestResponseLogHandler();
        }
        handleHeader(client);
        handleBody(client);
        return new RequestResponseLogHandler();
    }

    /**
     * 处理http请求头部
     */
    private void handleHeader(Client client) throws IOException {
        //处理分段下载
        handleRanges(client);
        //处理gzip压缩
        handleGZip(client);
        if (client.httpResponseMeta.contentLength >= 0) {
            client.httpResponseMeta.headers.put("Content-Length", Arrays.asList(client.httpResponseMeta.contentLength + ""));
        }
        if(client.serverConfigMeta.keepAlive){
            client.httpResponseMeta.headers.put("Connection", Arrays.asList("keep-alive"));
        }else{
            client.httpResponseMeta.headers.put("Connection", Arrays.asList("close"));
        }
        if (null == client.httpResponseMeta.headerString) {
            client.httpResponseMeta.fillHeadStream();
        }
    }

    /**
     * 处理http请求体
     */
    private void handleBody(Client client) throws IOException {
        if (null == client.httpResponseMeta.bodyInputStream) {
            return;
        }
        //返回报文信息到输出流
        if (client.httpResponseMeta.status != 206) {
            byte[] bytes = new byte[8192];
            int length = 0;
            while ((length = client.httpResponseMeta.bodyInputStream.read(bytes, 0, bytes.length)) != -1) {
                client.httpResponseMeta.outputStream.write(bytes, 0, length);
                client.httpResponseMeta.outputStream.flush();
            }
        } else {
            //分段下载返回
            String range = client.httpRequestMeta.headers.get("Range").get(0);
            int start = Integer.parseInt(range.substring(range.indexOf("=") + 1, range.indexOf("-")).trim());
            int end = range.endsWith("-") ? (int) client.httpResponseMeta.contentLength : Integer.parseInt(range.substring(range.indexOf("-") + 1));
            int size = end - start + 1;

            long skip = client.httpResponseMeta.bodyInputStream.skip(start);
            logger.trace("[分段传输]实际跳过字节数:{}", skip);

            byte[] bytes = new byte[8192];
            int actualLength = 0;
            int shouldReadLength = Math.min(size, bytes.length);
            while ((actualLength = client.httpResponseMeta.bodyInputStream.read(bytes, 0, shouldReadLength)) != -1) {
                client.httpResponseMeta.outputStream.write(bytes, 0, actualLength);
                size = size - bytes.length;
                if (size <= 0) {
                    break;
                }
                shouldReadLength = Math.min(size, bytes.length);
            }
        }

        client.httpResponseMeta.bodyInputStream.close();
        client.httpResponseMeta.outputStream.flush();
    }

    /**
     * 支持分段下载
     */
    private void handleRanges(Client client) {
        List<String> ranges = client.httpRequestMeta.headers.get("Range");
        if (null != ranges && ranges.size() > 0 && client.httpResponseMeta.contentLength > 0) {
            client.httpResponseMeta.httpStatus(HttpStatus.PARTIAL_CONTENT);
            String range = ranges.get(0);
            int start = Integer.parseInt(range.substring(range.indexOf("=") + 1, range.indexOf("-")).trim());
            int end = range.endsWith("-") ? (int) client.httpResponseMeta.contentLength : Integer.parseInt(range.substring(range.indexOf("-") + 1));
            int size = end - start + 1;
            if (size > client.httpResponseMeta.contentLength) {
                logger.warn("[Range头部文件大于实际文件]Range头部大小:{},实际文件大小:{}", size, client.httpResponseMeta.contentLength);
                throw new IllegalArgumentException("Range头部文件大于实际文件");
            }
            client.httpResponseMeta.contentLength = size;
            logger.trace("[PARTIAL_CONTENT响应]start:{},end:{},size:{},路径:{}", start, end, size, client.httpRequestMeta.uri);
            client.httpResponseMeta.headers.put("Content-Range", Arrays.asList("bytes " + start + "-" + end + "/" + size));
        }
    }

    /**
     * 处理gzip压缩
     */
    private void handleGZip(Client client) {
        if (null == client.httpResponseMeta.bodyInputStream) {
            logger.trace("[gzip压缩]无body内容,直接返回");
            return;
        }
        //分段下载时不进行gzip压缩
        if (client.httpResponseMeta.headers.containsKey("Accept-Ranges") || client.httpResponseMeta.headers.containsKey("Content-Range")) {
            logger.trace("[gzip压缩]分段下载时不进行gzip压缩");
            return;
        }
        //判断客户端是否支持gzip压缩
        if (!client.httpRequestMeta.headers.containsKey("Accept-Encoding") || !client.httpRequestMeta.headers.get("Accept-Encoding").get(0).contains("gzip")) {
            //客户端不支持gip压缩
            logger.trace("[gzip压缩]客户端不支持gzip压缩");
            return;
        }
        //大于2kb且指定类型文件进行gzip压缩
        if (client.httpResponseMeta.contentLength > 2048) {
            if (null == client.httpResponseMeta.contentType
                    || client.httpResponseMeta.contentType.startsWith("text/")
                    || client.httpResponseMeta.contentType.startsWith("application/")
                    || client.httpResponseMeta.contentType.startsWith("image/")
            ) {
                logger.trace("[开启gzip压缩]");
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(baos)) {
                    byte[] bytes = new byte[8192];
                    int length = 0;
                    while((length = client.httpResponseMeta.bodyInputStream.read(bytes,0,bytes.length))!=-1){
                        gzipOutputStream.write(bytes,0,length);
                    }
                    gzipOutputStream.finish();
                    client.httpResponseMeta.bodyInputStream = new ByteArrayInputStream(baos.toByteArray());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                client.httpResponseMeta.headers.put("Content-Encoding", Arrays.asList("gzip"));
                client.httpResponseMeta.contentLength = baos.size();
            }
        }
    }
}
